<?php
/**
 * view
 *
 * default abstract template-engine
 *
 * @created:	8 oct 2008
 * @updated:	12 oct 2008
 * @version:	0.2b
 * @use:		singleton
 * @link:		http://radiance-project.com/
 * @link:		http://qfox.ru/
 * @author:		alex <alex@qfox.ru> yaroshevich
 * @copyright:	(c) 2008-2011 radiance framework
 */

interface view_engine
{
	public function get_variables( );
	public function restore_variables( $data );
	public function clear_variables( );
	public function assign( $k, $v = null );
	public function render( $data = array() );
}

class view extends singleton
{
	// php singleton wrapper
	static public function &instance() { return parent::instance(__CLASS__); }
	
	private $__vars = array( );
	private $__defaults = array( );

	/**
	 * Extensions
	 * @var array
	 */
	protected $__extensions = array( );

	/**
	 * Engines
	 * @var array
	 */
	private $__engines = null;
	
	/**
	 * view constructor
	 */
	protected function __construct( )
	{
		$this ->add_view_engine( 'smarty', array( 'sma', 'tpl' ), new smarty_view_engine( $this ) );
		$this ->add_view_engine( 'php', array( 'php' ), new php_view_engine( $this ) );
		
		// Defaults init:
		$visitor = visitor::instance( );
		$this ->__defaults = array(
			'now'	=> time( ),
			'roles'	=> $visitor ->roles,
			'user'	=> $visitor ->viewuser,
			);
	}
	
	
	public function __set( $k, $v )
	{
		$this ->__vars[ $k ] = $v;
	}

	public function __get( $k )
	{
		return $this ->__vars[ $k ];
	}
	
	// back compatibility
	public function assign( $k, $v )
	{
		$this ->__set( $k, $v );
	}


	public function locate( $filenames )
	{
		$filenames = (array)( $filenames );
		foreach( $filenames as $v ) $filenames[] = strtolower( $v );
		$filenames = array_unique( $filenames );
        return fetch_file( rad_base_paths('views'), $filenames );
    }


	public function fetch( $templates, $params = null )
	{
		return $this ->display( $templates, $params );
	}
	
	public function display( $templates, $params = null )
	{
		//if ( @$templates[0] !== 'admin/goods/refbook/list.php' && $templates !== 'bits/tablelist.sma' && $templates !== 'admin/!index.tpl' && $templates !== '!assets.php' ) { var_dump($templates); die('asdasd'); }

		$__p = ( !is_null( $params ) ) ? $params : $this ->__vars;
		
		// fetch templates
		$templates = (array)( $templates );
		$__debug_template = join( ', ', $templates );
		
		$templates = (array)($templates);
		
		$all_templates = array( );
		foreach( $templates as $template ) {
			$ext = pathinfo( $template, PATHINFO_EXTENSION );
			$all_templates[] = str_replace( '.'.$ext, '.'.($ext?($ext=='php'?'php':$ext):'tpl'), $template );
			$all_templates[] = str_replace( '.'.$ext, '.'.($ext?($ext=='php'?'php':$ext):'tpl'), $template );
			$all_templates[] = str_replace( '.'.$ext, '.'.($ext=='php'?'tpl':'php'), $template );
		}

		$template = $this->locate( $all_templates );

		// check up for template
		if( $template === false )//( $template === false || strpos( basename( $template ), '_default' ) === 0 ) &&  )
		{
			if( request::instance()->is_developer() ) {
				// talk about non-exists template
				if (bc::enabled()) bc::log( 'template "'.join( '", "', $templates ).'" not found.' );
				$__p['__debug_template'] = $__debug_template;
				$template = array( '_default_terminator.tpl', '_default_terminator.php' );
			} else {
				$template = array( '_default.tpl', '_default.php' );
			}
			$template = $this->locate( $template );
		}

		// backup variables and cleanup
		$ve = $this ->get_view_engine( $template );
		$backup = $ve ->get_variables( );
		$ve ->clear_variables( );

		// apply default and current vars
		foreach( $this ->__defaults as $_k => $_v )
			$ve ->assign( $_k, $_v );
		
		if( is_array( $__p ) )
		foreach( $__p as $_k => $_v )
			$ve ->assign( $_k, $_v );
		
		$r = $ve ->render( );
		
		$ve ->restore_variables( $backup );

		return $r;
	}




	protected function add_view_engine( $name, $extensions, $instance )
	{
		if( !$instance instanceof view_engine )
			throw new Exception( 'added non view engine object instance' );
		
		foreach( $extensions as $extension )
			$this ->__extensions[$extension] = $name;
		
		$this ->__engines[$name] = $instance;
	}

	protected function determine_view_engine( $filename )
	{
		if( !file_exists( $filename ) || !is_readable( $filename ) )
			return null;
		
		$ext = pathinfo( $filename, PATHINFO_EXTENSION );
		if( !isset( $this ->__extensions[ $ext ] ) )
			return null;
		
		return $this ->__extensions[$ext];
	}

	protected function get_view_engine( $filename )
	{
		$name = $this ->determine_view_engine( $filename );
		
		if( !isset( $this ->__engines[$name] ) )
			return null;
		
		$ve = $this ->__engines[$name];
		$ve ->set_template( $filename );
		
		return $ve;
	}

};

class php_view_engine implements view_engine
{
	protected $view = null;
	protected $ob_level;
	protected $variables = array( );
	protected $template = null;
	protected $format = null;
	
	public function __construct( &$view )
	{
        $this->ob_level = ob_get_level();
		$this->view = $view;
		$this->format = formatter::instance( );
		$this->initialize( );
	}


    protected function initialize( )
	{
		$this ->variables = array( );
		$this ->template = null;
		return $this;
    }

	public function set_template( $filename )
	{
		$this ->template = $filename;
		return $this;
	}


	// interface
	public function __set( $k, $v )
	{
		$this ->variables[ $k ] = $v;
	}

	public function __get( $k )
	{
		return $this ->variables[ $k ];
	}
	
	public function assign( $k, $v = null )
	{
		$this ->__set( $k, $v );
	}



    public function render( $data = array() )
	{
        return $this->fetch($this ->template, $data, true);
    }

    public function display( $filename, $data = null )
	{
		switch(func_num_args())
		{
		case 1:
	        return $this->fetch( $this->template, $filename, false );
		case 2:
	        //return $this->fetch( $filename, $data, false );
			echo $this->view->display( $filename, $data );
			return true;
		}
		return false;
    }
	
    public function fetch($filename, $data = array(), $return = false)
    {
		ob_start();
		
		extract($this ->variables);
        extract($data);
        include($filename);

        $buffer = ob_get_contents();
        ob_end_clean();

        if ($return)
            return $buffer;

        if( ob_get_level() > $this ->ob_level )
            echo $buffer;
        else
            Response::instance() ->push( $buffer );
		
        return true;
    }
	
	// interface
	public function get_variables( )
	{
		return $this ->variables;
	}

	public function clear_variables( )
	{
		$this ->variables = array( );
		return $this;
	}

	public function restore_variables( $data )
	{
		$this ->variables = (array)( $data );
		$this ->template = null;
		return $this;
	}



	/**
	 * Modifiers
	 */

	/**
	 * Block caller
	 *
	 * @param string $action
	 * @param array $params
	 * @return string
	 */
	public function block( $action, $params = array( ) )
	{
//		$cache = cache::instance()->enable('view_block');
//		$cache->ttl(50);
//		$key = $cache->key('view::block',$action,$params);
//		if( false!==($r=$cache->$key) ) {
//			$cache->disable('view_block');
//			return $r;
//			}
		
		$action = controller::parse_action( $action );
		$c = controller::instance_of( $action['class'] );
		$r = $c instanceof controller
			? ($c ->__call( 'block_' . $action['method'], array( $params ) ))
			: sprintf('{%s::%s(...);}',$action['class'],$action['method']);
		
//		$cache->$key = $r;
//		$cache->disable('view_block');
		
		return $r;
	}

	public function escape( $s )
	{
		return htmlentities( $s, ENT_QUOTES, 'UTF-8' );
	}

	public function datef( $s, $f )
	{
		return $this ->smarty_modifier( 'datef', array( $s, $f ) );
	}
	
	public function format( $value, $type, $a = null, $b = null, $c = null )
	{
		return $this ->format ->$type( $value, $a, $b, $c );
	}
	
	public function avatar( $data )
	{
		return $this ->view ->display( 'avatar.php', $data );
	}

	// flashes
	public function flash( $key, $message = null )
	{
		visitor::instance( ) ->flash( $key, $message );
	}

	public function keep_flash( $key )
	{
		visitor::instance( ) ->keep_flash( $key );
	}

	public function display_flash_wrapper( $string, $key = null )
	{
		if( strpos($string,'</p>') === false && strpos($string,'class=') === false )
		{
			$class = (stripos($string,'error')||stripos($key,'error')) ? 'error'
				: (( stripos($string,'success')||stripos($key,'success')) ? 'success'
				: (( stripos($string,'update')||stripos($key,'update')) ? 'updated' : 'message' ) );
			
			$string = '<p class="'.$class.'">' . $string . '</p>';
		}
		
		return $string;
	}
	
	public function display_flash( $key = null )
	{
		if( !empty($key) ) {
			echo $this->display_flash_wrapper( visitor::instance( ) ->flash( $key ), $key );
		} else {
			$flashes = visitor::instance()->flash();
			echo join('',array_map(array($this,'display_flash_wrapper'),$flashes,array_keys($flashes)));
		}
	}

	public function inner_url($controller,$method,$data)
	{
		return controller::instance_of($controller)->inner_url($method,$data);
	}
	
	/**
	 * Smarty back-compatibility helpers
	 */
	protected function load_smarty_component( $type, $name )
	{
		$path = array( 'views/.plugins', 'classes/smarty/plugins' );
		$file = $type . '.' . $name . EXT;
		load_special_file( $file, $path );
	}
	
	public function _get_plugin_filepath( $a, $b )
	{
		throw new Exception( 'smarty not so smarty and calls file "'.$a.'.'.$b );
		die;
	}
	
	public function smarty_function( $name, $params )
	{
		
		$this ->load_smarty_component( 'function', $name );
		return call_user_func_array( 'smarty_function_'.$name, array( $params, &$this ) );
	}

	public function smarty_modifier( $name, $params )
	{
		$this ->load_smarty_component( 'modifier', $name );
		return call_user_func_array( 'smarty_modifier_'.$name, $params );
	}
	
}


class smarty_view_engine extends Smarty implements view_engine
{
	protected $view = null;
	protected $template = null;
	
	public function __construct( &$view )
	{
		parent::__construct( );
		$this->view = $view;
		$this ->initialize( );
	}

	/**
	 * Checks whether requested template exists.
	 *
	 * @param string $tpl_file
	 * @param string $base_path
	 * @return boolean
	 */
	public function template_exists( $tpl_file, $base_path=null )
	{
		$_params = array( 'resource_name' => $tpl_file, 'quiet'=>true, 'get_source'=>false );
		
		if( !is_null( $base_path ) ) {
			$_params['resource_base_path'] = $base_path;
		}
		
		return $this->_fetch_resource_info( $_params );
	}

	public function initialize( )
	{
		// Smarty init:
		$this ->template_dir= TPLPATH;
		$this ->cache_dir	= CPLPATH;
		$this ->compile_dir	= CPLPATH;
		$this ->config_dir	= CFGPATH;
		
		$this ->plugins_dir = array( );
		foreach(rad_base_paths() as $path)
			$this ->plugins_dir[] = $path.'views/.plugins/';
		$this ->plugins_dir[] = 'plugins';
		
		$this ->force_compile = true;
		$this ->caching = 0;
		$this ->cache_lifetime = 3600;
		$this ->cache_modified_check = 0;
		$this ->error_reporting = E_ALL^E_NOTICE;

		return $this;
	}

	// interface
	public function get_variables( )
	{
		return $this ->get_template_vars( );
	}

	public function clear_variables( )
	{
		$this ->clear_all_assign( );
		return $this;
	}

	public function restore_variables( $data )
	{
		$this ->clear_all_assign( );
		if( !empty( $data ) )
			$this ->assign( $data );
		
		return $this;
	}
	
	public function set_template( $filename )
	{
		$this ->template = $filename;
		return $this;
	}
	
    public function render($data = array(), $return = false)
	{
		$filename = $this ->template;
		$result = $this ->fetch( $filename );
		
		if( strpos( $filename, '!' ) !== false )
			return $result;
		
		return "<!-- smarty~$filename -->".$result."<!-- $filename~smarty -->";
	}
	
	/**
	 * Back-compatibility
	 */
	public function _smarty_include($params)
	{
		$params['smarty_include_tpl_file'] = $this->view->locate($params['smarty_include_tpl_file']);
		return parent::_smarty_include($params);
	}
}

